
SudokuGrid.prototype.strategyLockedCandidates = function ()
{
	console.log('=== Locked Candidates ===');
	var changed = false;

	var getter = function (obj) { return function (row, col) { return obj.notations[row][col]; } }(this);
	var setter = function (obj) { return function (row, col, value) { changed = true; obj.setNotation(row, col, value); } }(this);

	strategyLockedCandidates_ByRow(getter, setter);

	var getter_transposed = function (obj) { return function (col, row) { return obj.notations[row][col]; } }(this);
	var setter_transposed = function (obj) { return function (col, row, value) { changed = true; obj.setNotation(row, col, value); } }(this);

	strategyLockedCandidates_ByRow(getter_transposed, setter_transposed);

	return changed;
}

function strategyGeneric_LockedCandidatesEnforcer(row, triplet, getter, setter, mask)
{
	var HomeRow = CalcBoxHomeCell(row);
	var HomeCol = triplet * 3;
	// console.log('Intersection Reduction (' + row + ', ' + HomeCol +') to (' + row + ', ' + (HomeCol + 2) + ')' + mask.bitmaskToString());

	// Loop through line (row) of the box-line intersection.
	for (var c = 0; c < 9; c++)
	{
		if (c === HomeCol)
		{
			c += 2;
			continue;
		}

		setter(row, c, getter(row, c) & mask);
	}

	// Loop through the box of the box-line intersection.
	for (var r = HomeRow; r < HomeRow + 3; r++)
	{
		if (r === row) continue;
		for (var c = HomeCol; c < HomeCol + 3; c++)
		{
			setter(r, c, getter(r, c) & mask);
		}
	}
}

// This function enforces locked candidates. (Box-Line intersection)
// It assume it is a row-orientation triplet.
// To perform this function on column triplet, modify the getter and setter function so it transpose the values.
function strategyLockedCandidates_ByRow (getter, setter)
{
	// create and compute a 9 x 3 array caching the union of each of the triplets. 
	var TripletUnion = [];
	for (var r = 0; r < 9; r++)
	{
		TripletUnion[r] = [
				getter(r, 0) | getter(r, 1) | getter(r, 2),
				getter(r, 3) | getter(r, 4) | getter(r, 5),
				getter(r, 6) | getter(r, 7) | getter(r, 8)
			];
	}

	for (var r = 0; r < 9; r++)
	{
		switch (r % 3)
		{
			case 0:
				var r1 = r + 1, r2 = r + 2; break;
			case 1:
				var r1 = r - 1, r2 = r + 1; break;
			case 2:
				var r1 = r - 2, r2 = r - 1; break;
		}

		for (var t = 0; t < 3; t++)
		{
			var BoxMask = TripletUnion[r1][t] | TripletUnion[r2][t];

			switch (t)
			{
				case 0:
					var LineMask = TripletUnion[r][1] | TripletUnion[r][2]; break;
				case 1:
					var LineMask = TripletUnion[r][0] | TripletUnion[r][2]; break;
				case 2:
					var LineMask = TripletUnion[r][0] | TripletUnion[r][1]; break;
			}

			if (BoxMask !== LineMask)
			{
				strategyGeneric_LockedCandidatesEnforcer(r, t, getter, setter, BoxMask & LineMask)
			}
		}
	}
}

SudokuGrid.prototype.strategyDriver_APE = function (func, name)
{
	console.log('=== ' + name + ' ===');
	var changed = false;

	var getter = function (obj) { return function (row, col) { return obj.notations[row][col]; } }(this);
	var setter = function (obj) { return function (row, col, value) { changed = true; obj.setNotation(row, col, value); } }(this);

	var getter_transposed = function (obj) { return function (col, row) { return obj.notations[row][col]; } }(this);
	var setter_transposed = function (obj) { return function (col, row, value) { changed = true; obj.setNotation(row, col, value); } }(this);

	for (var i = 0; i < 9; i++)
	{
		for (var j = 0; j < 9; j += 3)
		{
			// row triplet
			func(i, j, getter, setter);

			// col triplet
			func(i, j, getter_transposed, setter_transposed); 
		}
	}
	return changed;
}

SudokuGrid.prototype.strategyAPE = function ()
{
	return this.strategyDriver_APE(strategyAPE_Triplet, 'Aligned Pair Exclusion');
}

// take a triplet and calculate all possible permutations of the triplet
// Loop thru peer cells to make sure none of the combination cause any of the peers to become invalid
function strategyAPE_Triplet(row, HomeCol, getter, setter)
{
	var CandidateList = [];
	var UnfilledCount = 3;
	for (c = HomeCol; c < HomeCol + 3; c++)
	{
		var candidates = getter(row, c).splitBinary();
		CandidateList.push(candidates)
		if (candidates.length == 1) UnfilledCount--;
	}
	// If none or only one of the triplets is unfilled, then this strategy is ineffective. 
	if (UnfilledCount < 2) return;

	// calculate all possible permutations of the triplet
	var Permutations = [];
	for (var a = 0, alen = CandidateList[0].length; a < alen; a++)
	{
		for (var b = 0, blen = CandidateList[1].length; b < blen; b++)
		{
			if (CandidateList[0][a] === CandidateList[1][b]) continue;

			for (var c = 0, clen = CandidateList[2].length; c < clen; c++)
			{
				if (((CandidateList[0][a] | CandidateList[1][b]) & CandidateList[2][c]) !== 0) continue;

				Permutations.push({
								candidates : [CandidateList[0][a], CandidateList[1][b], CandidateList[2][c]],
								union : CandidateList[0][a] | CandidateList[1][b] | CandidateList[2][c]
								});
			}
		}
	}

	Permutations.sort(function (a, b) { return a.union - b.union;});

	// generate a list of all peer values to check
	var Peers = [];
	for (var c = 0; c < 9; c++)
	{
		if (c === HomeCol)
		{
			c += 2;
		}
		else
		{
			Peers.push(getter(row, c));
			// console.log('Adding peer ' + Peers[Peers.length - 1].bitmaskToString());
		}
	}

	var HomeRow = CalcBoxHomeCell(row);
	for (var r = HomeRow; r < HomeRow + 3; r++)
	{
		if (r === row)
		{
			continue;
		}
		for (var c = HomeCol; c < HomeCol + 3; c++)
		{
			Peers.push(getter(r, c))
			// console.log('Adding peer ' + Peers[Peers.length - 1].bitmaskToString());
		}
	}
	//if (Peers.length !== 12) alert(Peers.length);

	// pass the candidate combinations thru the peers to make sure it doesn't cause peer to become invalid.
	var NoInvalid = true, CurrPermutation, CurrValid = true;
	permloop : for (var i = 0; i < Permutations.length; i++)
	{
		// we do not need to check the permutation if it is already marked invalid by a previous iteration
		if (Permutations[i].candidates === null) continue;

		// since we sorted the perm list previously
		// make sure we didn't just check this in the iteration right before
		if (Permutations[i].union === CurrPermutation)
		{
			// mark invalid if it was
			if (!CurrValid) Permutations[i].candidates = null;
			continue;
		}

		CurrPermutation = Permutations[i].union;
		CurrValid = true;

		for (var j = 0; j < 12; j++)
		{
			if ((Peers[j] & ~CurrPermutation) === 0)
			{
				Permutations[i].candidates = null;
				CurrValid = false;
				NoInvalid = false;
				// console.log('Triplet (' + row + ',' + HomeCol + ')' + CurrPermutation.bitmaskToString() + ' would invalidate peer ' + Peers[j].bitmaskToString());

				// we can skip the rest of the peers as this permutation is already invalid
				continue permloop;
			}
		}
	}

	if (NoInvalid) return;

	// recompute candidates
	var NewCandidates = [0, 0, 0];
	for (var i = 0, ilen = NewCandidates.length; i < ilen; i++)
	{
		for (var j = 0, jlen = Permutations.length; j < jlen; j++)
		{
			if (Permutations[j].candidates)
				NewCandidates[i] |= Permutations[j].candidates[i];
		}
	}
	for (var i = 0; i < 3; i++)
	{
		if (getter(row, HomeCol + i) !== NewCandidates[i])
		{
			// console.log('(' + row + ',' + (HomeCol + i) + ')' + getter(row, HomeCol + i).bitmaskToString() + ' to ' + NewCandidates[i].bitmaskToString());

			setter(row, HomeCol + i, NewCandidates[i]);
		}
	}
}
